﻿//Copyright (c) 2009, Andreas Grünwald
//All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Timers;
using System.Windows;

namespace yaTDL.Core.IO
{
    /// <summary>
    /// Provides the possibility to save and read a Collection of Projects to/from a text file. 
    /// The format used to do is described at http://yatdl.codeplex.com/Wiki/View.aspx?title=File%20Format
    /// <seealso cref="FileHandler"/> 
    /// </summary>    
    public class TextFileHandler:FileHandler
    {             

        /// <summary>
        /// Contains the default file name for file that will be used by OpenProjects() and SaveProjects() if no other filename 
        /// has been specified. The file should be located in defaultDirectory.
        /// </summary>
        /// <remarks>
        /// Implements a abstract property inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override string DefaultFileName
        {
            get
            {
                return DefaultDirectory + @"\TaskStore.txt";
            }
        }

        


        /// <summary>
        /// Initializes a new instance of TextualFileaccess().
        /// </summary>       
        public TextFileHandler()
        { }        

        /// <summary>
        /// Initializes a new instance of TextualFileaccess().
        /// </summary>
        /// <param name="enableDefaultFileMonitoring">Indicates whether the default data file is to be monitored for changes.</param>
        public TextFileHandler(bool enableDefaultFileMonitoring) : base(enableDefaultFileMonitoring) { }        
            
        
        StreamWriter streamWriter;

        StreamReader streamReader;


        private void writeLine(string line)
        {
            if(streamWriter == null)
            {
                streamWriter = new StreamWriter(DefaultFileName);
            }

            streamWriter.WriteLine(line);
        }

        private void readLines(string filePath)
        {
            lines.Clear();

            if (!File.Exists(filePath)) {return;}

            if(String.IsNullOrEmpty(File.ReadAllText(filePath))){return;}

            streamReader = new StreamReader(filePath);

            while (!streamReader.EndOfStream)       //writes the content of the file to a collection
            {
                lines.Add(streamReader.ReadLine());
            }

            streamReader.Close();    
        }
        

        

        /// <summary>
        /// Saves the specified data to the specified location.
        /// </summary>
        /// <param name="projects">A Collection of all Projects that are to be saved.</param>
        /// <param name="tasks"> Collection of all Tasks that are to be saved.</param>
        /// <param name="changes"> Collection of all Changes that are to be saved.</param>
        /// <param name="fileName">Specifies the full, absolut path of the file the data will be written to.</param>   
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override void SaveData(Collection<Project> projects, Collection<Task> tasks, Collection<Change> changes, string fileName)
        {
            if (!EnableWriting) { return; }


            if (defaultFile_FileSystemWatcher != null)
            { defaultFile_FileSystemWatcher.EnableRaisingEvents = false; }

            try
            {
                streamWriter = new StreamWriter(fileName);
            }
            catch (Exception)
            {
                SaveData(projects, tasks, changes, fileName);
                return;
            }

            writeLine("DateOfLastEdit|" + DateTime.Now.ToString());
            writeLine("AppVersion|" + getVersionString());
            writeLine("");


            foreach (Change c in changes)
            {
                writeLine("Change|");
                writeLine("cType|" + c.ChangedItemType.ToString());
                writeLine("cChangeTime|" + c.ChangeTime.ToString());
                writeLine("cHandled|" + c.HandledInSync.ToString());
                writeLine("cItemId|" + c.ItemId);
                writeLine("cKind|" + c.Kind.ToString());
                writeLine("cPropertyName|" + c.PropertyName);
                writeLine("");
            }


            for (int i2 = 0; i2 < projects.Count; i2++)
            {                              
                writeLine("Project|" + projects[i2].Name);       //begins a new project in the file and saves its name
                writeLine("pID|" + projects[i2].Id);             //saves the project's identifier
                writeLine("pLastEdited|" + projects[i2].LastEdited); //saves the Project's LastEdited property
                writeLine("pUserId|" + projects[i2].UserId);
                writeLine("pColor|" + projects[i2].Color);
                writeLine("pIndent|" + projects[i2].Indent.ToString());
                writeLine("pOrder|" + projects[i2].Order.ToString());
                writeLine("pCollapsed|" + projects[i2].Collapsed.ToString());
                writeLine("");

                var taskquery = from task in tasks
                                where task.ProjectId == projects[i2].Id
                                select task;

                foreach (Task t in taskquery)
                {
                    writeLine("Task|");
                    writeLine("Content|" + t.Content.Replace("|",""));
                    writeLine("Completed|" + t.Completed);
                    writeLine("DueDate|" + t.DueDate);
                    writeLine("Priority|" + t.Priority);
                    writeLine("ID|" + t.Id);
                    writeLine("UserId|" + t.UserId);
                    writeLine("LastEdited|" + t.LastEdited);
                    writeLine("Indent|" + t.Indent);
                    writeLine("Collapsed|" + t.Collapsed);
                    writeLine("Order|" + t.Order.ToString());
                    writeLine("Recurrence|" + t.Recurrence.AdvancedRepeat);
                    writeLine("Repeat|" + t.Recurrence.Repeat.ToString());
                    writeLine("");
                }

                //for (int i = 0; i < tasks.Count; i++)
                //{
                //    writeLine("Task|");
                //    writeLine("Content|" + tasks[i].Content.Replace("|",""));
                //    writeLine("Completed|" + tasks[i].InHistory);
                //    writeLine("DueDate|" + tasks[i].DueDate);
                //    writeLine("Priority|" + tasks[i].Priority);
                //    writeLine("ID|" + tasks[i].Id);
                //    writeLine("UserId|" + tasks[i].UserId);
                //    writeLine("LastEdited|" + tasks[i].LastEdited);
                //    writeLine("Indent|" + tasks[i].Indent);
                //    writeLine("Collapsed|" + tasks[i].Collapsed);
                //    writeLine("Order|" + tasks[i].Order.ToString());
                //    writeLine("Recurrence|" + tasks[i].Recurrence.DateString);
                //    writeLine("Repeat|" + tasks[i].Recurrence.Repeat.ToString());
                //    writeLine("");
                //}                                             
            }

            var tasksWithoutProject = from task in tasks
                    where String.IsNullOrEmpty(task.ProjectId) || task.ProjectId == "0"
                    select task;
            if (tasksWithoutProject.Any())
            {
                writeLine("Project|NoProject");
                writeLine("pID|0");
                writeLine("");

                foreach (Task t in tasksWithoutProject)
                {
                    writeLine("Task|");
                    writeLine("Content|" + t.Content.Replace("|", ""));
                    writeLine("Completed|" + t.Completed);
                    writeLine("DueDate|" + t.DueDate);
                    writeLine("Priority|" + t.Priority);
                    writeLine("ID|" + t.Id);
                    writeLine("UserId|" + t.UserId);
                    writeLine("LastEdited|" + t.LastEdited);
                    writeLine("Indent|" + t.Indent);
                    writeLine("Collapsed|" + t.Collapsed);
                    writeLine("Order|" + t.Order.ToString());
                    writeLine("Recurrence|" + t.Recurrence.AdvancedRepeat);
                    writeLine("Repeat|" + t.Recurrence.Repeat.ToString());
                    writeLine("");
                }
            }


            if (streamWriter != null)
            {
                streamWriter.Close();
            }

            if (defaultFile_FileSystemWatcher != null)
            { defaultFile_FileSystemWatcher.EnableRaisingEvents = true; }
        }
 
        /// <summary>
        /// Reads all Projects from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Projects found in the File.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Project> GetProjects(string fileName) 
        {
            Collection<Task> tasks;
            return getFileData(fileName, out tasks);            
        }

        /// <summary>
        /// Reads all Tasks from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Tasks found in the file.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Task> GetTasks(string fileName)
        {
            Collection<Task> tasks;
            getFileData(fileName, out tasks);
            return tasks;
        }

        /// <summary>
        /// Reads all Changes from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Chnages found in the file.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Change> GetChanges(string fileName)
        {
            Collection<int> linesOfChangeBeginnings = new Collection<int>();
            linesOfChangeBeginnings = searchFor("Change|");

            Collection<Change> changes = new Collection<Change>();
    
            foreach (int i in linesOfChangeBeginnings)
            {
                Change c = new Change();                
                int i2 = i;
                bool endReached = false;

                while (!endReached)
                {
                    string currentLine = lines[i2];
                    if (currentLine == "")
                    { endReached = true; }
                    else
                    {
                        switch (currentLine.Substring(0, currentLine.IndexOf("|") + 1))
                        {
                            case "cType|":
                                c.ChangedItemType = Type.GetType(currentLine.Substring(currentLine.IndexOf("|") + 1));             
                                break;
                            case "cChangeTime|":
                                c.ChangeTime = DateTime.Parse(currentLine.Substring(currentLine.IndexOf("|") + 1));
                                break;
                            case "cHandled|":
                                c.HandledInSync = bool.Parse(currentLine.Substring(currentLine.IndexOf("|") + 1));
                                break;
                            case "cItemId|":
                                c.ItemId = currentLine.Substring(currentLine.IndexOf("|") + 1);
                                break;
                            case "cKind|":
                                c.Kind = (ChangeKind) Enum.Parse(typeof(ChangeKind), currentLine.Substring(currentLine.IndexOf("|") + 1));                                
                                break;
                            case "cPropertyName|":
                                c.PropertyName = currentLine.Substring(currentLine.IndexOf("|") + 1);
                                break;
                        }
                    }
                    i2 += 1;
                }
                changes.Add(c);
            }

            return changes;

        }


        private Collection<Project> getFileData(string fileName, out Collection<Task> tasks)
        {

            #if !SILVERLIGHT
            if (defaultFile_FileSystemWatcher != null)
            {
                defaultFile_FileSystemWatcher.EnableRaisingEvents = false;
            }
            #endif

            
            Collection<Project> projects = new Collection<Project>();
             tasks = new Collection<Task>();

            readLines(fileName);

            if (!lines.Any()) { return projects; }
           

            if (checkCompatibility(fileName) == false)      //determines whether reading should be continued or not based on the version the file was created with
            { return projects; }                            //if  AppVersion should miss in the file, it will continue to read anyway


            

            Collection<int> linesOfProjectBeginnings = new Collection<int>();
            linesOfProjectBeginnings = searchFor("Project|");

            

            for (int i = 0; i < linesOfProjectBeginnings.Count; i++)
            {
                Collection<Task> projectTasks = new Collection<Task>();
                Project p;

                if (i != linesOfProjectBeginnings.Count - 1) //if i is not the index of the last beginning of a project, the lastLine is the line before the beginning of the next project
                {
                    p = readSingleProject(linesOfProjectBeginnings[i], linesOfProjectBeginnings[i + 1] - 1, out projectTasks);
                    if (!String.IsNullOrEmpty(p.Id) && p.Id != "0") { projects.Add(p); }
                }

                if (i == linesOfProjectBeginnings.Count - 1) //if i is the last beginnig of a project, the last line is the last of all lines
                {
                    p = readSingleProject(linesOfProjectBeginnings[i], lines.Count - 1, out projectTasks);
                    if (!String.IsNullOrEmpty(p.Id) && p.Id != "0") { projects.Add(p); }
                }

                foreach (Task t in projectTasks)
                {
                    tasks.Add(t);
                }

            }

            linesOfProjectBeginnings.Clear();

            #if !SILVERLIGHT
            if (defaultFile_FileSystemWatcher != null)
            {
                defaultFile_FileSystemWatcher.EnableRaisingEvents = true;
            }
            #endif
            
            return projects;            

        }



        /// <summary>
        /// Gets the DateTime of the specified file's last edit.
        /// </summary>
        /// <param name="fileName">The file to be read.</param>
        /// <returns>Returns a DateTime indicating when the specified file has been editet the last time.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override DateTime GetDateOfLastEdit(string fileName)
        {

            if (searchFor("DateOfLastEdit|", 0, 10).Any())
            {
                string line = lines[searchFor("DateOfLastEdit|", 0, 10)[0]];
                line = line.Substring(line.IndexOf("|") + 1);
                return DateTime.Parse(line);
            }
            else { return new DateTime(); }

        }



        
        private  Collection<String> lines = new Collection<string>();

        /// <summary>
        /// Describes the compatibility of a file. Files can have been created with a earlier or a newer version of yaTDL 
        /// or there could be no information about the version that created it in the file at all.
        /// </summary>
        /// <remarks>
        /// Currently this enum is only used by <see cref="checkCompatibility(string)"/>.
        /// </remarks>
        enum fileVersionInfo
        {
            wasCreatedWithEarlierVersion,
            wasCreatedWithNewerVersion,
            noInformationFound
        }



        /// <summary>
        /// Checks if the file that is supposed to be read is compatible with the current version of yaTDL.
        /// If it might not be compatible, it will ask the user if he wants to try reading it anyway.
        /// </summary>
        /// <returns>
        /// Returns true if the file was created by an earlier or the same version of yaTDL or if the user choose to try to read the file anyway.
        /// </returns>
        private  bool checkCompatibility(string fileName)
        {
            fileVersionInfo fileInfo;
            String line_version;

            if (!searchFor("AppVersion").Any()) { fileInfo= fileVersionInfo.noInformationFound; }
            else 
            { 
                line_version = lines[searchFor("AppVersion")[0]];
                line_version = line_version.Substring(line_version.IndexOf("|") + 1);
                if (String.IsNullOrEmpty(line_version)) { fileInfo = fileVersionInfo.noInformationFound; }
                else
                {
                    Version file_ProgramVersion = new Version(line_version);
                    Version this_ProgramVersion = new Version(getVersionString());
                    if (this_ProgramVersion.CompareTo(file_ProgramVersion) == -1) { fileInfo = fileVersionInfo.wasCreatedWithNewerVersion; }
                    else { fileInfo = fileVersionInfo.wasCreatedWithEarlierVersion; }
                }

            }

            if (fileInfo == fileVersionInfo.wasCreatedWithEarlierVersion) { return true; }
            else
            {
                if (fileName == DefaultFileName) 
                {
                   System.Windows.MessageBoxResult result = UI.Dialogs.ShowMessageBox("yaTDL could not make sure that the file used to store the data is compatible with this version.\n"+
                        "Would you like to save a copy of the file for the case something goes wrong?", "yaTDL",System.Windows.MessageBoxButton.YesNo);
                   

                   switch (result)
                   {                      
                       case System.Windows.MessageBoxResult.Yes:
                           System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog() 
                           {
                               FileName="TaskStore",
                               InitialDirectory=Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                               AddExtension = true,
                               DefaultExt = ".txt",
                               Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
                           };

                           dialog.ShowDialog();
                           if(Directory.Exists(Path.GetDirectoryName(dialog.FileName)))
                           {
                               File.Copy(DefaultFileName, dialog.FileName);
                           }
                           else
                           {
                               UI.Dialogs.ShowMessageBox("The operation was aborted and no backup was created","Error",MessageBoxButton.OK,MessageBoxImage.Error);
                           }                           
                           return true;
                       default:
                           return true;                          
                   }
                }
                else
                {
                    System.Windows.MessageBoxResult result = UI.Dialogs.ShowMessageBox("yaTDL could not make sure that file you are trying to open is compatible with this version, but you can try to open it anyway.\n"+
                    "If you try this, some data in the file might not be read correctly.\n" +
                    "Do you wish to continue?", "yaTDL", System.Windows.MessageBoxButton.YesNo);

                    switch (result)
                    {
                        case System.Windows.MessageBoxResult.Yes:
                            return true;
                        default:
                            return false;
                    }
                }
            }           
        }

        /// <summary>
        /// Returns a string with the AssemblyVersion of yaTDL.Core
        /// </summary>
        private static string getVersionString()
        {
            Assembly asm = Assembly.GetExecutingAssembly();
            return asm.FullName.Split(',')[1].Substring(9);
        }


        /// <summary>
        /// Searches for a certain value in all items of <see cref="lines"/>.
        /// </summary>
        /// <param name="value">The value to search for.</param>
        /// <returns>Returns a Collection of ints. Every item is the index of a line that contains the indicated value.</returns>
        private  Collection<int> searchFor(string value)
        {
            return searchFor(value, 0, lines.Count - 1);
        }

        /// <summary>
        /// Searches for certain values in the indicated range of <see cref="lines"/>.
        /// </summary>
        /// <param name="value">The value to search for.</param>
        /// <param name="startIndex">The line where the search is supposed to start.</param>
        /// <param name="endIndex">The line where the search is supposed to end.</param>
        /// <returns>Returns a Collection of Ints. Every item is the index of a line that contains the indicated value.</returns>
        private  Collection<int> searchFor(string value, int startIndex, int endIndex)
        {
            Collection<int> linesThatContain = new Collection<int>();

            for (int i = startIndex; i <= endIndex; i++)
            {
                if (lines[i].Contains(value))
                {
                    linesThatContain.Add(i);
                }
            }

            return linesThatContain;
        }
        

        /// <summary>
        /// Creates a instance of <see cref="Project"/> from a block of lines that contain data of one Project.
        /// </summary>
        /// <param name="firstLine">The index of the first line containing data of the Project.</param>
        /// <param name="lastLine">The index of the last line containing data of the Project.</param>
        /// <returns>Returns a Project containing all data that could be read from the indicated range of lines.</returns>
        private Project readSingleProject(int firstLine, int lastLine, out Collection<Task> tasks)
        {
            Project project = new Project(lines[firstLine].Substring(lines[firstLine].IndexOf("|")+1));
            tasks = new Collection<Task>();


            Collection<int> beginnigsOfTasks = searchFor("Task|", firstLine, lastLine);

            int data_start = firstLine;
            int data_end = !beginnigsOfTasks.Any() ? lastLine : beginnigsOfTasks[0];
            

            for (int i = data_start; i < data_end; i++)
            {               
                string line = lines[i];
                switch (line.Substring(0, line.IndexOf("|") + 1))
                {
                    case "pID|":
                        project.Id = line.Substring(line.IndexOf("|") + 1);
                        break;
                    case "pLastEdited|":
                        project.LastEdited = DateTime.Parse(line.Substring(line.IndexOf("|") + 1));
                        break;
                    case "pUserId|":
                        project.UserId = line.Substring(line.IndexOf("|") + 1);
                        break;
                    case "pColor|":
                        project.Color = line.Substring(line.IndexOf("|") + 1);
                        break;
                    case "pIndent|":
                        project.Indent = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                        break;
                    case "pOrder|":
                        project.Order = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                        break;
                    case "pCollapsed|":
                        project.Collapsed = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                        break;
                }
            }


            for (int i = 0; i < beginnigsOfTasks.Count; i++)
            {
                if (i != beginnigsOfTasks.Count - 1) //if i is not the index of the last beginning of a project, the lastLine is the line before the beginning of the next project
                {
                    Task t = readSingleTask(beginnigsOfTasks[i], beginnigsOfTasks[i + 1] - 1);
                    t.ProjectId = project.Id;
                    tasks.Add(t);
                }

                if (i == beginnigsOfTasks.Count - 1) //if i is the last beginnig of a project, the last line is the last of all lines
                {
                    Task t = readSingleTask(beginnigsOfTasks[i], lastLine);
                    t.ProjectId = project.Id;
                    tasks.Add(t);
                }
            }

            return project;
        }

        /// <summary>
        /// Creates a new Task from a range of lines that contain data ofkann one Task.
        /// </summary>
        /// <param name="firstLine">The first line containing data about the Task.</param>
        /// <param name="lastline">The last line containing data about the task.</param>
        /// <returns>Returns a Task containing all data that could be read from the indicated range of lines.</returns>
        private  Task readSingleTask(int firstLine, int lastline) 
        {
            Task task = new Task();

            for(int i=firstLine; i<=lastline;i++) //reads the information a saves it in task
            {
                string line = lines[i];  //the current line                         

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Content|")
                 {                     
                     task.Content = line.Substring(line.IndexOf("|") + 1);                      
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Completed|")
                 {                    
                     task.Completed = bool.Parse(line.Substring(line.IndexOf("|") + 1));
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Priority|")
                 {
                     task.Priority = int.Parse(line.Substring(line.IndexOf("|") + 1));                                   
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "DueDate|")
                 {
                     task.DueDate = DateTime.Parse(line.Substring(line.IndexOf("|") + 1));
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "ID|")
                 {
                     task.Id = line.Substring(line.IndexOf("|") + 1);
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "UserId|")
                 {
                     task.UserId = line.Substring(line.IndexOf("|") + 1);
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "LastEdited|")
                 {
                     task.LastEdited = DateTime.Parse(line.Substring(line.IndexOf("|") + 1));
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Indent|")
                 {
                     task.Indent = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                 } 
                
                 if (line.Substring(0, line.IndexOf("|") + 1) == "Collapsed|")
                 {
                     task.Collapsed = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Order|")
                 {
                     task.Order = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Recurrence|")
                 {
                     task.Recurrence.AdvancedRepeat = line.Substring(line.IndexOf("|") + 1);
                 }

                 if (line.Substring(0, line.IndexOf("|") + 1) == "Repeat|")
                 {
                     task.Recurrence.Repeat = Int32.Parse(line.Substring(line.IndexOf("|") + 1));
                 }                
            }
            
            return task;
        }

      

              
    }
}
